package it.netsphere.nswing.comp;

import it.netsphere.nswing.util.PropertyMethodMap;
import it.redstar.annotated.swing.ITableRowColor;

import java.awt.Color;
import java.awt.Component;
import java.lang.reflect.Method;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.SwingConstants;
import javax.swing.border.Border;
import javax.swing.border.EtchedBorder;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableColumnModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;

import ognl.Ognl;

public class NSTable extends JTable {

	private static final long serialVersionUID = 1135573475126009478L;
	private static org.slf4j.Logger LOG = org.slf4j.LoggerFactory
			.getLogger(NSTable.class);

	private boolean autoResize;
	private boolean sorterEnable;
	private List<ColumnProperty> columns;
	private List<?> data;
	private DateFormat dateFormat;
	private boolean editable;

	public NSTable() {
		setModel(new HTableModel());
		setDefaultRenderer(Object.class, new MyDefaultTableRenderer());
		// setDefaultRenderer(Object.class, new RowHeaderRenderer(this));
		((DefaultTableCellRenderer) getTableHeader().getDefaultRenderer())
				.setHorizontalAlignment(SwingConstants.CENTER);
	}

	public Object getSelectedItem() {
		int row = getSelectedRow();
		if (row < 0)
			return null;

		return getData().get(row);
	}

	private class ColumnProperty {
		String title;
		String property;
		boolean editable;

		public String getTitle() {
			return title;
		}

		public void setTitle(String title) {
			this.title = title;
		}

		public String getProperty() {
			return property;
		}

		public void setProperty(String property) {
			this.property = property;
		}

		public boolean isEditable() {
			return editable;
		}

		public void setEditable(boolean editable) {
			this.editable = editable;
		}

		public String toString() {
			return String.format("ColumnProperty[%s,%s,%s]", getTitle(),
					getProperty(), isEditable());
		}

	}

	/**
	 * parse this line: "{property,description}..."
	 * 
	 * @param s
	 */

	public void setColumnProperties(String s) {
		if (s == null) {
			setColumns(null);
			return;
		}

		List<ColumnProperty> cols = new ArrayList<ColumnProperty>();

		Pattern p = Pattern
				.compile("\\{([\\w\\.]+),([\\w\\sàòùì]+)(,(true|false))?\\}");
		Matcher m = p.matcher(s);
		int pos = 0;
		while (m.find(pos)) {
			pos += m.group().length();
			ColumnProperty cp = new ColumnProperty();
			cp.setProperty(m.group(1));
			cp.setTitle(m.group(2));
			if (m.groupCount() >= 4)
				cp.setEditable(Boolean.valueOf(m.group(4)));
			cols.add(cp);
		}

		setColumns(cols);
	}

	public int getRows() {
		if (getData() == null)
			return 0;
		return getData().size();
	}

	private class HTableModel extends AbstractTableModel {

		private static final long serialVersionUID = 6600039144035324090L;

		@Override
		public int getColumnCount() {
			return getColumns() == null ? 0 : getColumns().size();
		}

		@Override
		public int getRowCount() {
			return getRows();
		}

		@Override
		public String getColumnName(int col) {
			if (getColumns() == null)
				return super.getColumnName(col);

			return getColumns().get(col).getTitle();
		}

		@Override
		public boolean isCellEditable(int row, int col) {
			if (isEditable() == false)
				return false;

			if (getColumns() == null)
				return false;

			return getColumns().get(col).isEditable();
		}

		@Override
		public Object getValueAt(int rowIndex, int columnIndex) {
			Object o = getData().get(rowIndex);
			ColumnProperty col = getColumns().get(columnIndex);

			try {
				return getValue(o, col.getProperty());
			} catch (Exception e) {
				return "<N/A>";
			}
		}

		public void setValueAt(Object v, int rowIndex, int columnIndex) {
			try {
				Object o = getData().get(rowIndex);
				ColumnProperty col = getColumns().get(columnIndex);
				String prop = col.getProperty();
				if (prop.indexOf('.') > 0)
					prop = prop.substring(0, prop.indexOf('.'));

				Ognl.setValue(prop, o, v);
				// LOG.debug( v );
			} catch (Exception e) {
				LOG.error(e.getMessage());
			}

		}

	}

	public List<ColumnProperty> getColumns() {
		return columns;
	}

	public void setColumns(List<ColumnProperty> columns) {
		this.columns = columns;
		((AbstractTableModel) getModel()).fireTableStructureChanged();

	}

	public List<?> getData() {
		return data;
	}

	public void setData(List<?> data) {
		this.data = data;
		if (isAutoResize())
			autoResizeColWidth();
		((AbstractTableModel) getModel()).fireTableDataChanged();
		((DefaultTableCellRenderer) getTableHeader().getDefaultRenderer())
				.setHorizontalAlignment(SwingConstants.CENTER);

	}

	public void removeDataElement(Object o) {
		getData().remove(o);
		((AbstractTableModel) getModel()).fireTableDataChanged();
	}

	private static Object[] NO_ARGS = new Object[0];
	private PropertyMethodMap methodMap = new PropertyMethodMap();

	/**
	 * 
	 * @param o
	 * @param property
	 * @return
	 * @throws Exception
	 */

	private Object getValue(Object o, String property) throws Exception {
		if (o == null)
			throw new NullPointerException("object is null");
		if (property == null)
			throw new NullPointerException("property is null");

		String prop1 = property;
		String prop2 = null;
		int idx = property.indexOf('.');
		if (idx > 0) {
			prop1 = property.substring(0, idx);
			prop2 = property.substring(idx + 1);
		}

		Method m = methodMap.getReadMethod(o.getClass(), prop1);
		if (m == null)
			throw new NullPointerException("method not found for " + prop1
					+ " and class " + o.getClass());
		Object res = m.invoke(o, NO_ARGS);

		if (res == null)
			return null;

		// if(res==null && prop2!=null)
		// throw new NullPointerException("value is null");

		if (prop2 != null)
			return getValue(res, prop2);

		return res;
	}

	public void autoResizeColWidth() {
		setAutoResizeMode(getRows() > 0 ? JTable.AUTO_RESIZE_OFF
				: JTable.AUTO_RESIZE_ALL_COLUMNS);

		int margin = 5;

		for (int i = 0; i < getColumnCount(); i++) {
			int vColIndex = i;
			DefaultTableColumnModel colModel = (DefaultTableColumnModel) getColumnModel();
			TableColumn col = colModel.getColumn(vColIndex);
			int width = 0;

			// Get width of column header
			TableCellRenderer renderer = col.getHeaderRenderer();

			if (renderer == null) {
				renderer = getTableHeader().getDefaultRenderer();
			}

			Component comp = renderer.getTableCellRendererComponent(this,
					col.getHeaderValue(), false, false, 0, 0);

			width = comp.getPreferredSize().width;

			// Get maximum width of column data
			for (int r = 0; r < getRowCount(); r++) {
				renderer = getCellRenderer(r, vColIndex);
				comp = renderer.getTableCellRendererComponent(this,
						getValueAt(r, vColIndex), false, false, r, vColIndex);
				width = Math.max(width, comp.getPreferredSize().width);
			}

			// Add margin
			width += 2 * margin;

			// Set the width
			col.setPreferredWidth(width);
		}

		((DefaultTableCellRenderer) getTableHeader().getDefaultRenderer())
				.setHorizontalAlignment(SwingConstants.LEFT);

		// table.setAutoCreateRowSorter(true);
		getTableHeader().setReorderingAllowed(false);

	}

	public boolean isAutoResize() {
		return autoResize;
	}

	public void setAutoResize(boolean autoResize) {
		this.autoResize = autoResize;
	}

	/*
	 * 
	 */

	private static Border EDIT_BORDER = new EtchedBorder();

	private class MyDefaultTableRenderer extends DefaultTableCellRenderer {
		private static final long serialVersionUID = -5098261437930959603L;

		public Component getTableCellRendererComponent(JTable table,
				Object value, boolean isSelected, boolean hasFocus, int row,
				int column) {
			Component comp = super.getTableCellRendererComponent(table, value,
					isSelected, hasFocus, row, column);

			ColumnProperty col = getColumns().get(column);
			// comp.setBackground( col.isEditable()? EDIT_CELL_BACKGROUND_COLOR:
			// Color.WHITE );
			if (comp instanceof JLabel && col.isEditable())
				((JLabel) comp).setBorder(EDIT_BORDER);
			// LOG.debug( comp.getClass().getSuperclass().getSuperclass() );

			int row2 = row;
			if (isSorterEnable()) {
				row2 = getRowSorter().convertRowIndexToModel(row);
			}

			Object o = getData().get(row2);
			if (o instanceof ITableRowColor) {
				ITableRowColor tc = (ITableRowColor) o;
				Color color = tc.getRowColor(isSelected, hasFocus);
				if (color != null)
					comp.setBackground(color);
			}

			return comp;
		}
	}

	/**
	 * 
	 */

	public boolean isEditable() {
		return editable;
	}

	public void setEditable(boolean editable) {
		this.editable = editable;
	}

	public DateFormat getDateFormat() {
		return dateFormat;
	}

	public void setDateFormat(DateFormat dateFormat) {
		this.dateFormat = dateFormat;
	}

	public boolean isSorterEnable() {
		return sorterEnable;
	}

	public void setSorterEnable(boolean sorterEnable) {
		this.sorterEnable = sorterEnable;
		if (sorterEnable) {
			TableRowSorter<TableModel> sorter = new TableRowSorter<TableModel>(
					getModel());
			setRowSorter(sorter);
		}
	}

}
